using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;

namespace PoshHttp
{
   [Cmdlet(VerbsCommon.Get, "Web", SupportsShouldProcess = true, DefaultParameterSetName="Simple")]
   public class GetWebCommand : PSCmdlet
   {

      #region Parameters
      [Parameter(Position = 0,
          Mandatory = true,
          ValueFromPipeline = true,
          HelpMessage = "URI or Path to HTML file")]
        [ValidateNotNullOrEmpty]
        [Alias("Url","Uri")]
        public Uri Name
        {
           get { return _uri; }
           set { _uri = value; }
        }
        Uri _uri;

        [Parameter(Position = 1,
         Mandatory = true,
         ValueFromPipeline = true,
         ParameterSetName = "Post",
         HelpMessage = "Parameter set")]
        [ValidateNotNullOrEmpty]
        public Hashtable PostParameters
        {
           get { return _post; }
           set { _post = value; }
        }
        Hashtable _post;


        [Parameter(Position = 2,
         Mandatory = true,
         ValueFromPipeline = true,
         ParameterSetName = "Get",
         HelpMessage = "Parameter set")]
        [ValidateNotNullOrEmpty]
        public Hashtable GetParameters
        {
           get { return _get; }
           set { _get = value; }
        }
        Hashtable _get;



        [Parameter(Position = 3,
            Mandatory = false,
            ValueFromPipeline = false,
            HelpMessage = "FilePath to save to")]
        [Alias("FilePath", "FileName")]
        public String Path
        {
           get { return _path; }
           set { _path = value; }
        }
        String _path = null;

        [Parameter(Position = 10, HelpMessage = "Save To File")]
        public SwitchParameter SaveToFile
        {
           get { return _save; }
           set { _save = value; }
        }
        SwitchParameter _save;

        [Parameter(Position = 11, HelpMessage = "Plain Text (don't convert to XML)")]
        public SwitchParameter PlainText
        {
           get { return _plain; }
           set { _plain = value; }
        }
        SwitchParameter _plain;

        [Parameter(Position = 12, HelpMessage = "Force overwrite files")]
        public SwitchParameter Force
        {
           get { return _force; }
           set { _force = value; }
        }
        SwitchParameter _force;
      #endregion

      protected override void ProcessRecord()
      {
         switch (ParameterSetName)
      	{
            case "Post":   {
               HandleResponse(PostUrl(_uri, _post));
            } break;
            case "Get":    {
               HandleResponse(GetUrl(_uri, _get));
            } break;
            case "Simple": {
               HandleResponse(GetUrl(_uri));
            } break;
	      }
      }


      private CookieContainer myCookies = new CookieContainer();

      /// <summary>
      /// Synchronously retrieves the HTML document from the specified Uri and returns it as an XmlDocument.
      /// </summary>
      /// <param name="url">The Uri.</param>
      /// <returns>An XML representation of the document</returns>
      protected HttpWebResponse GetUrl(Uri uri, Hashtable data)
      {
         if (!string.IsNullOrEmpty(uri.Query)  && uri.Query.Length > 0)
         {
            uri = new Uri(uri.PathAndQuery + "&" + Utility.BuildQueryString(data));
         }
         else
         {
            uri = new Uri(uri.AbsoluteUri + "?" + Utility.BuildQueryString(data));
         }
         return GetUrl(uri);
      }


      protected HttpWebResponse GetUrl(Uri uri)
      {
         // set up the request with cookies and such
         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
         request.CookieContainer = myCookies;
         request.UserAgent = UserAgent;
         request.Method = "GET";

         return (HttpWebResponse)request.GetResponse();
      }



      protected HttpWebResponse PostUrl(Uri uri, Hashtable data)
      {
         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
         // $bytes = [Text.Encoding]::UTF8.GetBytes( $bytes )
         request.CookieContainer = myCookies;
         request.UserAgent = UserAgent;
         request.Method = "POST";

         string post = Utility.BuildQueryString(data);
         request.ContentType = "application/x-www-form-urlencoded";
         request.ContentLength = post.Length;
         using (StreamWriter rw = new StreamWriter(request.GetRequestStream()))
         {
            rw.Write(post);
            rw.Flush();
            rw.Close();
         }

         return (HttpWebResponse)request.GetResponse();
      }

      protected string GetFilePath(HttpWebResponse response)
      {
         string path = null;
         //if (_save.ToBool() || _path != null)
         //{
            try
            {
               // if there's a valid specified path
               if (_path != null && SessionState.Path.IsValid(_path))
               {
                  //ProviderInfo provider;
                  //PSDriveInfo drive;
                  // ToDo: there's probably a better way, but I'm in a hurry
                  path = SessionState.Path.GetUnresolvedProviderPathFromPSPath(_path); //, out provider, out drive);
                  //if (paths.Count > 0 && provider == SessionState.Path.CurrentFileSystemLocation.Provider)
               }
               else
               {
                  if (path != null)
                  {
                     WriteError(new ErrorRecord(
                        new FileNotFoundException("Can't parse Path parameter!"),
                              "Bad Path",
                              ErrorCategory.InvalidArgument,
                              _path));
                  }

                  string fileName = null;
                  if (response.GetResponseHeader("Content-Disposition").Length > 0)
                  {
                     Match m = Regex.Match(
                                       response.GetResponseHeader("Content-Disposition"),
                                       "(?i)filename=(.*)$");
                     if (m.Groups.Count > 0)
                     {
                        fileName = m.Groups[1].Value.Trim('\\', '/', '"', '\'');
                     }
                  }
                  if (fileName == null)
                  {
                     fileName = response.ResponseUri.Segments[response.ResponseUri.Segments.Length - 1].Trim('\\', '/', '"', '\'');
                  }

                  if (fileName == null)
                  {
                     //ToDo: fileName = Read-Host "Please provide a file name"
                     fileName = System.IO.Path.GetRandomFileName();
                  }
                  if ((new FileInfo(fileName)).Extension.Length == 0)
                  {
                     fileName = fileName + "." + response.ContentType.Split(';')[0].Split('/')[1];
                  }

                  path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.ProviderPath, fileName);
               }
            }
            catch (Exception ex)
            {
               WriteError(new ErrorRecord(ex, "Bad Path", ErrorCategory.InvalidArgument, _path));
            }

            FileInfo info = new FileInfo(path);
            if (info.Directory.Exists)
               return path;
            else
               return null;

         //}
         //else return null;
      }



      protected void HandleResponse( HttpWebResponse response ) {
         string path = GetFilePath(response);
         if (!_plain.ToBool() && (response.ContentType.Contains("html") || response.ContentType.Contains("xml")))
         {
            XmlDocument doc = new XmlDocument();
            using (StreamReader rr = new StreamReader(response.GetResponseStream()))
            {
               doc = Utility.ConvertHtml(rr);
               rr.Close();
            }

            if (!String.IsNullOrEmpty(path) && (_save.ToBool() || !string.IsNullOrEmpty(_path) ))
            {
               if (!System.IO.File.Exists(path) || _force.ToBool() || this.ShouldContinue(path, "File exists, overwrite?"))
               {
                  doc.Save(path);
                  WriteObject(new FileInfo(path));
               }
               else WriteError(new ErrorRecord(
                        new FileNotFoundException("Can't overwrite file"),
                              "File Exists",
                              ErrorCategory.InvalidArgument,
                              _path));
            } 
            else WriteObject(doc);
         }
         else if (!_save.ToBool() 
            && string.IsNullOrEmpty(_path) 
            && (_plain.ToBool() || response.ContentType.Contains("text")) )
         {
            using (StreamReader rr = new StreamReader(response.GetResponseStream()))
            {
               //string line;
               while (!rr.EndOfStream)
               {
                  //line = rr.ReadLine();
                  //if (!string.IsNullOrEmpty(line))
                  //{
                  //   WriteObject(line);
                  //}
                  WriteObject(rr.ReadLine());
               }
            }
         }
         else 
         {
            if (!System.IO.File.Exists(path) || _force.ToBool() || ShouldContinue(path, "File exists, overwrite?"))
            {
               byte[] buffer = new byte[4096];
               int count;

               using (Stream read = response.GetResponseStream())
               {
                  using (FileStream write = new FileStream(path, FileMode.Create))
                  {
                     do
                     {
                        count = read.Read(buffer, 0, buffer.Length);
                        write.Write(buffer, 0, count);
                     } while (count > 0);
                     write.Flush();
                     write.Close();
                  }
               }
               WriteObject(new FileInfo(path));

            } else WriteError(new ErrorRecord(
                        new FileNotFoundException("Can't write file"),
                              "Bad Path",
                              ErrorCategory.InvalidArgument,
                              _path));

         }
      }

      private static string agentString = null; 
      public static string UserAgent
      {
         get
         {
            if (agentString == null)
            {
               System.Reflection.AssemblyName app = System.Reflection.Assembly.GetExecutingAssembly().GetName();

               agentString = String.Format("Mozilla/4.0 (compatible; MSIE 7.0; {0}{1} {2}.{3}.{4}; {6} {7}.{8}.{9}; .NET CLR {5})",
                  Environment.OSVersion.Platform.ToString().Contains("Win") ? "Windows" : "Unix",
                  Environment.OSVersion.Platform.ToString().Contains("NT") ? " NT" : "",
                  Environment.OSVersion.Version.Major,
                  Environment.OSVersion.Version.Minor,
                  Environment.OSVersion.Version.Build, //, Environment.OSVersion.ServicePack 
                  Environment.Version, //.ToString().Substring(0, Environment.Version.ToString().LastIndexOf('.')),
                  app.Name, app.Version.Major, app.Version.Minor, app.Version.Build
               );
            }
            return agentString;
         }
      }
   }
}
